/**
 * SMTP server.
 *
 * Sam Thorogood (c) 2010
 */

package main

import (
  "fmt"
  "flag"
  "os"
  "runtime"
  "strings"
  "delivers"
)

var (
  fHelp *bool = flag.Bool("h", false, "to show help")
  fBindTo *string = flag.String("l", "localhost:60000", "address to bind on")
  fBasePath *string = flag.String("p", "", "deliverd basepath")
  fToLocalUsers *bool = flag.Bool("local", false, "deliver to local users as fallback")
  fWelcomeMessage *string = flag.String("w", "Welcome to DeliverD", "welcome message")
  fHost *string // defined in init()
)

func init() {
  // Grab os.Hostname and back the hostname flag onto it by default.
  h, err := os.Hostname()
  if err != nil {
    panic(err)
  }
  fHost = flag.String("host", h, "hostname to identify as")
}

func main() {
  // TODO: Would be nice to add compile date/time to this.
  fmt.Fprintln(os.Stdout, "DeliverD, built with Go", runtime.Version())
  fmt.Fprintln(os.Stdout, "(c) 2010 Sam Thorogood (sam.thorogood@gmail.com)")

  flag.Parse()
  if *fHelp {
    flag.PrintDefaults()
    return
  }
  if *fBasePath == "" {
    fmt.Fprintln(os.Stderr, "fatal; please specify -p for basepath")
    return
  }

  // Listen won't return until the server shuts down.
  err := Listen(*fBindTo, client)
  if err != nil {
    fmt.Fprintln(os.Stderr, "fatal; couldn't bind", *fBindTo, "err:", err)
    return
  }
}

func NewFlagState() delivers.State {
  return delivers.NewState(*fBasePath, *fToLocalUsers, *fHost)
}

/**
 * Handler for all incoming clients.
 */
func client(client Client) {
  s := NewFlagState()

  client.Write(fmt.Sprintf("200 %s", *fHost))
  for {
    line := client.Read()
    if line == nil {
      return
    }

    cmd, rest := MatchCommand(*line)
    switch (cmd) {
    case "quit":
      client.Write("221 Bye")
      return
    case "noop":
      client.Write("250 OK")
    case "helo":
      client.Write("250 OK")
    case "ehlo":
      // TODO: determine supported extensions
      exts := []string{"UTF8STMP", "8BITMIME"}
      if len(exts) > 0 {
        client.Write(fmt.Sprintf("250-%s", *fWelcomeMessage))
        for i, ext := range exts {
          format := "250-%s"
          if i == len(exts) - 1 {
            format = "250 %s"
          }
          client.Write(fmt.Sprintf(format, ext))
        }
      } else {
        client.Write("250 " + *fWelcomeMessage)
      }
    case "rcpt":
      ok, address, _ := MatchAddressFormat("to", rest)
      if !ok {
        client.Write("501 Invalid format")
      } else {
        err := s.To(address)
        switch err {
        case delivers.NoError:
          client.Write("250 OK")
        case delivers.AlreadySpecified:
          client.Write("550 Address already specified")
        case delivers.NotHere:
          client.Write("550 Not at this host")
        default:
          client.Write("501 Unknown error")
        }
      }
    case "mail":
      ok, address, _ := MatchAddressFormat("from", rest)
      if !ok {
        client.Write("501 Invalid format")
      } else {
        err := s.From(address)
        switch err {
        case delivers.NoError:
          client.Write("250 OK")
        case delivers.AlreadySpecified:
          client.Write("550 From address already specified")
        default:
          client.Write("501 Unknown error")
        }
      }
    case "rset":
      s = NewFlagState()
      client.Write("250 OK")
    case "data":
      if s.GetFrom() == nil {
        client.Write("503 Send MAIL FROM")
      } else if len(s.GetTo()) == 0 {
        client.Write("503 Send RCPT TO")
      } else {
        client.Write("354 Start mail input; end with <CRLF>.<CRLF>")
        // TODO: get tmp file, open and write there
        for {
          line := client.Read()
          if line == nil {
            return
          }
          tmp := *line
          tmp = strings.TrimSpace(tmp)
          line = &tmp

          if strings.HasPrefix(*line, "..") {
            tmp := *line
            tmp = tmp[1:]
            line = &tmp
          } else if *line == "." {
            break
          }
          fmt.Fprintln(os.Stdout, *line)
        }
        // TODO: put new mail into delivery queue
        client.Write("250 OK")
      }
    default:
      client.Write("500 ERR")
    }
  }
  return
}
